package model;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;

import matrix.GraphDoorLink;
import matrix.GraphNode;
import matrix.NodeLinkMatrix;
import model.path.PathConstraint;
import model.path.PathFactory;
import model.path.PathStopPoint;
import model.path.Point4D;
import model.trajectory.BaseTrajectoryModel;
import model.trajectory.TargetModel;
import model.trajectory.TrajectoryModel;
import org.xml.sax.SAXException;

import settings.Settings;
import ui.FileUtils;
import utils.Pair;
import utils.TimeIntervalle;

import colors.Colors;

import data.Names;
import data.XmlLoader;

public class MainModel 
{	
	public long minTime = -1;
	public long maxTime = 0;
	
	public ArrayList<TargetModel> targets;
	public ArrayList<RoomModel> rooms;

	public ArrayList<DoorModel> doors;
	public ArrayList<AdjacencyModel> adjacencies;

	public final long sInMillis = 1000;
	public final long mnInMillis = 60 * sInMillis;
	public final long hInMillis = 60 * mnInMillis;
	public final long dInMillis = 24 * hInMillis;
	
	public NodeLinkMatrix matrix;
	
	public MainModel()
	{			
		try 
		{	
			Hashtable<Integer, RoomModel> roomsHashtable = XmlLoader.LoadRooms("./bin/data/rooms_dataset2.dat");
				
			this.rooms = new ArrayList<RoomModel>(roomsHashtable.values());
					
			this.doors = XmlLoader.LoadDoors(roomsHashtable, "./bin/data/doors_dataset2.dat");
			
			this.matrix = new NodeLinkMatrix();
			
			for(Iterator<RoomModel> it = roomsHashtable.values().iterator(); it.hasNext();)
			{
				RoomModel room = it.next();
				this.matrix.addKey(new GraphNode(room));
			}
			for(Iterator<DoorModel> itDoor = doors.iterator(); itDoor.hasNext();)
			{
				DoorModel door = itDoor.next();
				GraphNode firstNode = null;
				GraphNode secondNode = null;
				for(Iterator<GraphNode> it= this.matrix.getKeys().iterator(); it.hasNext();)
				{
					GraphNode node = it.next();
					if(node.getRoom() == door.FirstRoom)
					{
						firstNode = node;
					}
					else if(node.getRoom() ==  door.SecondRoom)
					{
						secondNode = node;
					}				
				}
				this.matrix.add(new GraphDoorLink(door, firstNode, secondNode));
			}
			
			this.adjacencies = XmlLoader.LoadAdjacencies(roomsHashtable, "./bin/data/adjacencies_dataset2.dat");
			XmlLoader.LoadInnerGraphMatrices(roomsHashtable, doors, "./bin/data/inner_room_nodeLink.dat");
		}
		catch (SAXException e) 
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		} 
		catch (ParserConfigurationException e) 
		{
			e.printStackTrace();
		}
	}
	
	public void loadTrajectories()
	{
		this.clearModel();
		
		long today = (System.currentTimeMillis() / dInMillis) ;
		today = today * dInMillis - 19 * hInMillis;
		
		
		if(true)
		{
			this.loadDataset(Settings.datasetExportFolderPath + "/dataset_41.expLog");
		}
		else if(Settings.GenerateRandomTargets)
		{
			for(int i = 0; i < Settings.nbTargets; i ++ )
			{
				TrajectoryModel traj;
				TargetModel target;
				Stack<PathStopPoint> stopPoints = new Stack<PathStopPoint>();
				
				
				long beginTime = (long) (today + 10 * hInMillis +   (Math.random() * 30) * mnInMillis);
				long delay =  (long) (Math.random() * 10 * hInMillis );
				long endTime = delay + beginTime;
					
				int beginRoomIndex = ((int) (Math.random() * rooms.size() - 1));
				while(this.rooms.get(beginRoomIndex).isCorridor())
				{
					beginRoomIndex = ((int) (Math.random() * rooms.size() - 1));
				}
				RoomModel beginRoom = rooms.get(beginRoomIndex);
				PathStopPoint beginStopPoint = new PathStopPoint(beginRoom.getRandomInterestPoint());

				int endRoomIndex = ((int) (Math.random() * rooms.size() - 1));
				while(endRoomIndex == beginRoomIndex || this.rooms.get(endRoomIndex).isCorridor())
				{
					endRoomIndex = ((int) (Math.random() * rooms.size() - 1));	
				}
				RoomModel endRoom = this.rooms.get(endRoomIndex);
				PathStopPoint endStopPoint = new PathStopPoint(endRoom.getRandomInterestPoint());

				
				int nbRoomCheckPoint = (int) (Math.random() * Settings.NbStopPointPerTrajectory);	
				int lastRoomIndex = beginRoomIndex;		
				long timeStep = delay / (nbRoomCheckPoint + 1);
				
				for(int j = 0; j < nbRoomCheckPoint; j++)
				{
					int index = ((int) (Math.random() * rooms.size() - 1));
					while(lastRoomIndex == index || (j == nbRoomCheckPoint && index == endRoomIndex))
					{
						index = ((int) (Math.random() * rooms.size() - 1));
					}
					lastRoomIndex = index;
					
					RoomModel randomRoom = this.rooms.get(index);
							
					if(randomRoom.getInterestPointCount() > 1)
					{
						GraphNode lastNode = null;		
						int nbStopPointInsideRoom =  (int) (1 + Math.random() * 10);
						
						for(int k  = 0; k < nbStopPointInsideRoom; k++)
						{
							GraphNode randomNode = randomRoom.getRandomInterestPoint();
							while(randomNode == lastNode)
							{
								randomNode = randomRoom.getRandomInterestPoint();
							}
							long stayTime = (long) ((Math.random() * 10 + 5) * mnInMillis);
							PathStopPoint pathStopPoint = new PathStopPoint(randomNode, stayTime );
							stopPoints.add(pathStopPoint);
						}
					}
					else
					{
						GraphNode randomNode = randomRoom.getRandomInterestPoint();
						long stayTime = (long) ((Math.random() * 10 + 5) * mnInMillis);
						PathStopPoint pathStopPoint = new PathStopPoint(randomNode, stayTime);
						stopPoints.add(pathStopPoint);	
					}
				}	
				PathConstraint constraint = new PathConstraint();
				constraint.setBeginPoint(beginStopPoint);
				constraint.setEndPoint(endStopPoint);
				constraint.addStopPoints(stopPoints);
				
				PathFactory factory = new PathFactory(this.matrix);
				factory.setContstraints(constraint);

				traj = new TrajectoryModel(i, this, factory.generatePath(), beginTime);
				target = new TargetModel(i, Names.getName());
				target.setTrajectory(traj);
				target.setColor(Colors.NextColorSet());
				traj.setColor(target.getColor());
				
				targets.add(target);
			}
		}
		else
		{
			PathConstraint constraint = new PathConstraint();	
			
			long beginTime = (long) (today + 10 * hInMillis +   (Math.random() * 30) * mnInMillis);
			long delay =  (long) (10 * hInMillis );
			long endTime = delay + beginTime;
	
			RoomModel beginRoom = this.getRoomById(2101);
			PathStopPoint beginStopPoint = new PathStopPoint(beginRoom.getRandomInterestPoint());
			PathStopPoint endStopPoint = new PathStopPoint(beginRoom.getRandomInterestPoint());

			constraint.setBeginPoint(beginStopPoint);
			constraint.setEndPoint(endStopPoint);

			RoomModel firstStopPointRoom = this.getRoomById(4102);
			RoomModel secondStopPointRoom = this.getRoomById(1301);
			
			PathStopPoint stopPoint = new PathStopPoint(firstStopPointRoom.getRandomInterestPoint(), ( 1 * hInMillis));
			PathStopPoint stopPoint2 = new PathStopPoint(secondStopPointRoom.getRandomInterestPoint(),( 1 * hInMillis));
			PathStopPoint stopPoint3 = new PathStopPoint(firstStopPointRoom.getRandomInterestPoint(), (1* hInMillis));
			
			constraint.addStopPoint(stopPoint);
			constraint.addStopPoint(stopPoint2);
			constraint.addStopPoint(stopPoint3);
			
			PathFactory factory = new PathFactory(this.matrix);
			factory.setContstraints(constraint);

			TrajectoryModel traj = new TrajectoryModel(0, this, factory.generatePath(), beginTime);
			TargetModel target = new TargetModel(0, Names.getName());
			target.setTrajectory(traj);
			target.setColor(Colors.NextColorSet());
			traj.setColor(target.getColor());
			targets.add(target);
		}
		this.ComputeModel();
	}
	
	public ArrayList<DoorModel> getDoors(RoomModel firstRoom, RoomModel secondRoom)
	{
		ArrayList<DoorModel> result = new ArrayList<DoorModel>();
		for(Iterator<DoorModel> it = this.doors.iterator(); it.hasNext();)
		{
			DoorModel door = it.next();
			if( (door.FirstRoom == firstRoom && door.SecondRoom == secondRoom)
			 || (door.FirstRoom == secondRoom && door.SecondRoom == firstRoom) )
			{
				result.add(door);
			}
		}
		return result;
	}
	
	public void ComputeModel()
	{
		ArrayList<Long> milestones = new ArrayList<Long>();
		
		for(Iterator<TargetModel> it = targets.iterator(); it.hasNext();)
		{
			TargetModel currentTarget = it.next();
			TrajectoryModel trajectory = currentTarget.traj;
			if(this.minTime == -1)
			{
				this.minTime = trajectory.minTime;
			}
			if(trajectory.minTime < this.minTime)
			{	
				this.minTime = trajectory.minTime;
			}
			if(trajectory.maxTime > this.maxTime)
			{
				this.maxTime = trajectory.maxTime;
			}
			
			for(Iterator<Pair<TimeIntervalle, BaseTrajectoryModel>> itIntervalle = trajectory.subTrajectories.iterator(); itIntervalle.hasNext(); )
			{
				TimeIntervalle intervalle = itIntervalle.next().key;
				milestones.add(intervalle.endTime);
			}
		}
		Collections.sort(milestones);
		
		ArrayList<TimeIntervalle> intervalles = new ArrayList<TimeIntervalle>();

		Long beginTime = (long) 0;
		for(Iterator<Long> it = milestones.iterator(); it.hasNext();)
		{
			Long endTime = it.next();
			if(endTime > beginTime)
			{
				intervalles.add(new TimeIntervalle(beginTime, endTime));
			}
			beginTime = endTime;
		}
		
		
		for(Iterator<TimeIntervalle> it = intervalles.iterator(); it.hasNext();)
		{
			TimeIntervalle intervalle = it.next();
			
			for(Iterator<RoomModel> itRooms = rooms.iterator(); itRooms.hasNext();)
			{
				RoomModel room = itRooms.next();
				room.getTargetsByIntervalle().add(new Pair<TimeIntervalle, ArrayList<TargetModel>>(intervalle, new ArrayList<TargetModel>()));
				
				for(Iterator<TargetModel> itTargets = targets.iterator(); itTargets.hasNext();)
				{
					TargetModel target = itTargets.next();
					TrajectoryModel traj = target.traj;
					RoomModel resultRoom = traj.getRoom(intervalle);
					if(resultRoom != null  && resultRoom.getID() == room.getID())
					{
						room.addTarget(intervalle, target);
					}
				}
			}
			
			for(Iterator<DoorModel> itDoors = doors.iterator(); itDoors.hasNext();)
			{
				DoorModel door = itDoors.next();
				door.targetsByIntervalle.add(new Pair<TimeIntervalle, ArrayList<TargetModel>>(intervalle, new ArrayList<TargetModel>()));
				
				for(Iterator<TargetModel> itTargets = targets.iterator(); itTargets.hasNext();)
				{
					TargetModel target = itTargets.next();
					TrajectoryModel traj = target.traj;
					DoorModel resultDoor = traj.getTransition(intervalle);
					if(resultDoor != null  && resultDoor == door)
					{
						door.addTarget(intervalle, target);
					}
				}
			}
		}
		
		System.out.println("OKKKKKKKKKKKK");
	}

	public RoomModel getRoomById(int id)
	{
		for(Iterator<RoomModel> it = rooms.iterator(); it.hasNext();)
		{
			RoomModel room = it.next();
			if(room.getID() == id)
			{
				return room;
			}
		}
		return null;
	}

	public void saveVisibleTrajectory() {
		// TODO Auto-generated method stub
		File[] datasetFiles = FileUtils.findFiles(Settings.datasetExportFolderPath);
		int fileCount = FileUtils.count("dataset_", datasetFiles);
		File datasetFile = new File(Settings.datasetExportFolderPath + "dataset_" + fileCount + ".expLog");
		try 
		{
			datasetFile.createNewFile();
			FileWriter fw = new FileWriter(datasetFile, true);
			BufferedWriter output = new BufferedWriter(fw);
			int i = 0;
			for(Iterator<TargetModel> it = this.targets.iterator(); it.hasNext();)
			{
				TargetModel target = it.next();
				if(target.isVisible())
				{
					String[] lines = target.traj.toString().split("\n");
					for(int j = 0; j < lines.length; j ++)
					{
						output.write("Track_" + i + ";" + j + ";" + lines[j] + "\n");
					}
					output.flush();
					i++;
				}
			}
			output.close();
 
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	
	public void loadDataset(String path)
	{
		File file = new File(path);
		InputStream inputStream;
		try 
		{
			inputStream = new FileInputStream(file);
			InputStreamReader streamReader =new InputStreamReader(inputStream);
			BufferedReader br= new BufferedReader(streamReader);
			
			String ligne;
			
			int currentId = 0;
			
			String currentTrackId = null;
			ArrayList<Point4D> currentList = new ArrayList<Point4D>();
			while ( (ligne=br.readLine()) != null )
			{	
				String[] values = ligne.split(";");
				String trackName = values[0];
				if(currentTrackId == null)
				{
					currentTrackId = trackName; 
				}
				
				if(!currentTrackId.equals(trackName))
				{
					TargetModel target = new TargetModel(currentId, currentTrackId);
					TrajectoryModel trajectory = new TrajectoryModel(currentId++, this, currentList);
					target.setTrajectory(trajectory);
					target.setColor(Colors.NextColorSet());
					trajectory.setColor(target.getColor());
					this.targets.add(target);
					
					currentList.clear();
					currentTrackId = null;
				}
				
				currentList.add(new Point4D(this, values[2], values[3], values[4], values[5]));
			}
			br.close();
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void clearModel()
	{
		this.minTime = -1;
		this.maxTime = 0;
		
		this.targets.clear();
	}

	private RoomModel getDoorById(int id)
	{
		for(Iterator<RoomModel> it = rooms.iterator(); it.hasNext();)
		{
			RoomModel room = it.next();
			if(room.getID() == id)
			{
				return room;
			}
		}
		return null;
	}
}
